/*
 * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
 * Copyright (C) 2024  Mio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
module pd.utils;

import std.net.curl : HTTP;

/// User Agent header that is used for requests to www.pixiv.net.
enum UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.1";

/// Create a `std.net.curl.HTTP` client prepared for requests to pixiv.
///
/// Params:
///    phpsessid = The PHPSESSID cookie value.
///    headers   = Custom headers to use for this request.
///
/// There are some default headers that will be used if they're not
/// present in `headers`:
///
///   * `accept:  application/json, */*`
///   * `host:    www.pixiv.net`
///   * `referer: https://www.pixiv.net`
///
/// Returns: A HTTP client with the appropriate headers set for making
///          requests to the pixiv servers.
HTTP makeHTTPClient(string phpsessid, string[string] headers = string[string].init)
{
   import std.experimental.logger: warningf;

   auto client = HTTP();

   headers.require("accept", "application/json, */*");
   headers.require("host", "www.pixiv.net");
   headers.require("referer", "https://www.pixiv.net");

   foreach(const ref header, const ref value; headers) {
      client.addRequestHeader(header, value);
   }

   client.onReceiveStatusLine = (HTTP.StatusLine statusLine) {
      if (statusLine.code < 200 || statusLine.code > 299) {
         // Shame you can't access the URL for the client...
         warningf("Received non-200 status code %d (%s)", statusLine.code, statusLine.reason);
      }
   };

   client.setCookie("PHPSESSID=" ~ phpsessid);
   client.setUserAgent(UserAgent);
   return client;
}

///
/// Attempt to make *path* safe for different operating systems.
///
/// This will replace anything that is not: a hyphen ('-'), a period ('.'),
/// an underscore ('_'), or a "word character" (includes numbers).  Any
/// characters found will be replaced by a hyphen.
///
/// Only tested on Linux and Android.
///
/// Params:
///   path = The input path to replace characters with.
///
/// Returns: A new string with the replacements made.
///
string makeSafe(string path)
{
   import std.regex : regex, replaceAll;
   import std.string : chomp, replace, strip;

   enum kReplacement = "-";

   // 2.076 compat: reg must not be 'const'. Doesn't work with replaceAll.
   auto reg = regex(`[^-\w._]`);
   const noSpaces = strip(path).replace(" ", kReplacement);
   const almostSafe = replaceAll(noSpaces, reg, kReplacement);

   return chomp(almostSafe, "._-");
}

/**
 * Suspend the current thread for a random value between *lowest* and
 * *largest*.
 *
 * Params:
 *   lowest = The lowest value to use for the random sleep duration.
 *   largest = The largest value to use for the random sleep duration.
 *   print = Print a message to `stderr` with sleep duration.
 */
void sleep(int lowest, int largest, bool print = true)
{
    import core.thread : Thread;
    import core.time : seconds;
    import std.random : Random, uniform, unpredictableSeed;

    auto seed = Random(unpredictableSeed);
    const secs = uniform(lowest, largest + 1, seed);
    if (print) {
      import std.stdio : stderr;
      stderr.writefln("Sleeping for %d seconds...", secs);
    }
    Thread.sleep(secs.seconds);
}

struct StopSpinnerMessage {}

void runSpinner(string prefix)
{
   import core.time : msecs;
   import core.thread : Thread;
   import std.concurrency : receiveTimeout;
   import std.stdio : stderr;
   import mlib.term;

   const delay = 100.msecs;
   const segments = ['|', '/', '-', '\\'];
   bool running = true;

   while (running) {
      for (auto i = 0; i < segments.length; ++i) {
         receiveTimeout(1.msecs,
            (StopSpinnerMessage _) {
               running = false;
               return;
            }
         );
         if (!running) {
            break;
         }
         stderr.writef("\r\033[2K%s %c", prefix, segments[i]);
         stderr.flush();
         Thread.sleep(delay);
      }
   }
}
